package third.lib.wx.pay.app;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.*;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import third.lib.common.exception.ThirdException;
import third.lib.common.status.ThirdStatus;
import third.lib.wx.common.utils.WXUtils;
import third.lib.wx.pay.app.model.PayResource;
import third.lib.wx.pay.app.model.WxAppPayOrderV3;
import third.lib.wx.pay.app.model.WxAppPayRetV3;
import third.lib.wx.pay.app.model.WxPayProduct;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.security.*;
import java.time.Instant;
import java.util.Base64;

public class WxAppPayV3 {
    private final Logger logger = LoggerFactory.getLogger(WxAppPayV3.class);

    private String mchId;
    private String serialNo;
    private String keyPath;
    private String v3Key;
    private String appId;
    private String notifyUrl;
    private PrivateKey privateKey;

    WechatPayHttpClientBuilder builder;
    AutoUpdateCertificatesVerifier verifier;

    public WxAppPayV3(String mchId, String serialNo, String keyPath, String v3Key, String appId, String notifyUrl) {
        this.mchId = mchId;
        this.serialNo = serialNo;
        this.v3Key = v3Key;
        this.keyPath = keyPath;
        this.appId = appId;
        this.notifyUrl = notifyUrl;
        try {
            InputStream stream = new FileInputStream(this.keyPath);
            byte[] v3keyutf8 = this.v3Key.getBytes("utf-8");
            this.privateKey = PemUtil.loadPrivateKey(stream);
            this.verifier = new AutoUpdateCertificatesVerifier(
                    new WechatPay2Credentials(this.mchId, new PrivateKeySigner(this.serialNo, privateKey)),
                    v3keyutf8);
            this.builder = WechatPayHttpClientBuilder.create()
                    .withMerchant(this.mchId, this.serialNo, privateKey)
                    .withValidator(new WechatPay2Validator(verifier));
            stream.close();
        } catch (FileNotFoundException | UnsupportedEncodingException ex) {
            ex.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public WxAppPayOrderV3 unifiedorder(int amount, String desc, String tradeNo) throws ThirdException {
        CloseableHttpClient httpClient = this.builder.build();
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/app");
        WxPayProduct product = new WxPayProduct();
        product.setAppid(this.appId);
        product.setAmount(new WxPayProduct.WxPayAmount(amount, "CNY"));
        product.setDescription(desc);
        product.setMchid(this.mchId);
        product.setNotify_url(this.notifyUrl);
        product.setOut_trade_no(tradeNo);

        String json = JSON.toJSONString(product);
        System.out.println(json);
        StringEntity entity = new StringEntity(json, "UTF-8");
        // post请求是将参数放在请求体里面传过去的;这里将entity放入post请求体中
        httpPost.setEntity(entity);
        httpPost.setHeader("Content-Type", "application/json");
        httpPost.setHeader("Accept", "application/json");
        httpPost.setHeader("User-Agent", "hb-server");

        CloseableHttpResponse response = null;
        try{
            response = httpClient.execute(httpPost);
            int status = response.getStatusLine().getStatusCode();
            if(status == 200){
                HttpEntity responseEntity = response.getEntity();
                if (responseEntity != null) {
                    String contentStr = EntityUtils.toString(responseEntity);
                    JSONObject object = JSON.parseObject(contentStr);
                    if(object.get("code") != null){
                        logger.error(contentStr);
                        throw new ThirdException(ThirdStatus.WX);
                    }else{
                        Object preObj = object.get("prepay_id");
                        if(preObj == null){
                            throw new ThirdException(ThirdStatus.WX);
                        }
                        String timestamp = String.valueOf(Instant.now().getEpochSecond());
                        String nonceStr = WXUtils.getNonceStr();
                        WxAppPayOrderV3 v3 = new WxAppPayOrderV3();
                        v3.setAppid(this.appId);
                        v3.setPrepayid(preObj.toString());
                        v3.setPack("Sign=WXPay");
                        v3.setPartnerid(this.mchId);
                        v3.setNoncestr(nonceStr);
                        v3.setTimestamp(timestamp);
                        v3.setSign(sign(timestamp, nonceStr, preObj.toString()));
                        return v3;
                    }
                }else{
                    logger.error("回应异常: HttpEntity");
                    throw new ThirdException(ThirdStatus.WX);
                }
            }else{
                logger.error("状态错误: " + status);
                throw new ThirdException(ThirdStatus.WX);
            }
        }catch (IOException ex){
            throw new ThirdException(ThirdStatus.NET);
        }finally {
            try{
                httpClient.close();
                if(response != null){
                    response.close();
                }
            }catch (IOException ex){
                logger.error(ex.getMessage());
            }
        }
    }

    public WxAppPayRetV3 payCbVerify(HttpServletRequest request) throws ThirdException {
        StringBuffer sb = new StringBuffer();
        String sign = request.getHeader("Wechatpay-Signature");
        logger.info("sign", sign);
        String serial = request.getHeader("Wechatpay-Serial");
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        try {
            InputStream inputStream = request.getInputStream();
            BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
            String s;
            while ((s = in.readLine()) != null) {
                sb.append(s);
            }
            in.close();
            String body = sb.toString();
            String content = timestamp + "\n" + nonce + "\n" + body + "\n";
            boolean bVerify = this.verifier.verify(serial, content.getBytes("utf-8"), sign);
            if(bVerify){
                JSONObject object = JSON.parseObject(body);
                JSONObject resource = (JSONObject) object.get("resource");
                PayResource payResource = new PayResource();
                payResource.setAlgorithm(resource.getString("algorithm"));
                payResource.setAssociatedData(resource.getString("associated_data"));
                payResource.setCiphertext(resource.getString("ciphertext"));
                payResource.setOriginalType(resource.getString("original_type"));
                payResource.setNonce(resource.getString("nonce"));
                AesUtil aesUtil = new AesUtil(this.v3Key.getBytes("utf-8"));
                String message = aesUtil.decryptToString(payResource.getAssociatedData().getBytes("utf-8"),
                        payResource.getNonce().getBytes("utf-8"), payResource.getCiphertext());
                WxAppPayRetV3 wxAppPayRetV3 = new WxAppPayRetV3();
                JSONObject jsonObject = JSON.parseObject(message);
                String appId = jsonObject.getString("appid");
                String mchId = jsonObject.getString("mchid");
                String tradeState = jsonObject.getString("trade_state");
                if(!appId.equals(this.appId) || !mchId.equals(this.mchId)){
                    logger.error("验证失败: {}", message);
                    throw new ThirdException(ThirdStatus.WX);
                }
//                if(!tradeState.equals("SUCCESS")){
//                    logger.error("验证失败: {}", message);
//                    throw new ThirdException(ThirdStatus.WX);
//                }
                wxAppPayRetV3.setAppid(appId);
                wxAppPayRetV3.setMchid(mchId);
                wxAppPayRetV3.setTradeNo(jsonObject.getString("out_trade_no"));
                wxAppPayRetV3.setTransactionId(jsonObject.getString("transaction_id"));
                wxAppPayRetV3.setTradeState(jsonObject.getString("trade_state"));
                wxAppPayRetV3.setTradeStateDesc(jsonObject.getString("trade_state_desc"));
                JSONObject map = (JSONObject)jsonObject.get("amount");
                wxAppPayRetV3.setTotalAmount((Integer) map.get("total"));
                wxAppPayRetV3.setPayAmount((Integer) map.get("payer_total"));
                return wxAppPayRetV3;
            }else{
                logger.error("验证失败: {}", body);
                throw new ThirdException(ThirdStatus.WX);
            }
        } catch (IOException | GeneralSecurityException | JSONException e) {
            logger.error(e.getMessage());
            throw new ThirdException(ThirdStatus.WX);
        }
    }

    private String sign(String timestamp, String noncestr, String prepayId) throws ThirdException {
        StringBuilder builder = new StringBuilder();
        builder.append(this.appId);
        builder.append("\n");
        builder.append(timestamp);
        builder.append("\n");
        builder.append(noncestr);
        builder.append("\n");
        builder.append(prepayId);
        builder.append("\n");
        try{
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(this.privateKey);
            sign.update(builder.toString().getBytes("utf-8"));
            return Base64.getEncoder().encodeToString(sign.sign());
        } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | UnsupportedEncodingException e) {
            logger.error(e.getMessage());
            throw new ThirdException(ThirdStatus.WX);
        }
    }

    public String Success(){
        return "{   \n" +
                "    \"code\": \"SUCCESS\",\n" +
                "    \"message\": \"成功\"\n" +
                "}";
    }

    public String Fail(){
        return "{   \n" +
                "    \"code\": \"Fail\",\n" +
                "    \"message\": \"失败\"\n" +
                "}";
    }
}
